/*!
 Temelia - Queue implementation source file.

 Copyright (C) 2008, 2009 Ceata (http://ceata.org/proiecte/temelia).

 @author Dascalu Laurentiu

 This program is free software; you can redistribute it and
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 3
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "include/queue.h"
#include "include/common.h"
#include "include/doubly_linked_list.h"

#include <stdlib.h>

struct _queue_t
{
	/*! keys number of queue */
	int capacity;

	/*! the dimension of the queue */
	int size;

	/*!
	 * value on which the reallocation depends ; 1 if you want
	 * that the queue to increase in size when it's full and 0 otherwise
	 */
	int capacity_increment;

	/*! front of queue */
	doubly_linked_list_iterator_t begin;

	/*! node before the end of queue */
	doubly_linked_list_iterator_t end;
};

queue_t queue_new(int capacity)
{
	queue_t queue;

	_ASSERT(capacity, <=, 0, INVALID_INPUT, NULL);

	queue = (struct _queue_t *) _new(sizeof(struct _queue_t));

	_ASSERT(queue, ==, NULL, NULL_POINTER, NULL);

	queue->capacity = capacity;
	queue->size = 0;
	queue->capacity_increment = 1;
	queue->end = queue->begin = NULL;

	return queue;
}

void queue_delete(queue_t queue)
{
	doubly_linked_list_iterator_t it, aux;

	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	if (queue->end == NULL)
	{
		queue->capacity = queue->size = queue->capacity_increment = 0;
		_delete(queue);
		return;
	}

	for (it = queue->end; it != NULL;)
	{
		aux = it;
		it = doubly_linked_list_iterator_get_next(it);
		doubly_linked_list_iterator_delete(aux);
	}

	queue->capacity = 0;
	queue->capacity_increment = 0;
	queue->size = 0;
	queue->end = queue->begin = NULL;

	_delete(queue);
}

int queue_get_capacity(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, -1);

	return queue->capacity;
}

int queue_get_size(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, -1);

	return queue->size;
}

int queue_get_capacity_increment(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, -1);

	return queue->capacity_increment;
}

int queue_is_empty(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, -1);

	return queue->size == 0;
}

int queue_is_full(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, -1);

	return queue->capacity == queue->size;
}

void queue_push_front(queue_t queue, void *x)
{
	doubly_linked_list_iterator_t aux;

	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	if (!queue->capacity_increment && queue_is_full(queue))
	{
		temelia_errno = FULL;
		return;
	}

	aux = doubly_linked_list_iterator_new(x, NULL, NULL);

	if (queue_is_empty(queue))
	{
		queue->end = queue->begin = aux;
		queue->size = 1;
		return;
	}

	doubly_linked_list_iterator_join(queue->begin, aux, NULL);
	queue->begin = aux;

	queue->size++;
	if (queue->size > queue->capacity)
		queue->capacity = queue->size;
}

void queue_push_back(queue_t queue, void *x)
{
	doubly_linked_list_iterator_t aux;

	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	if (!queue->capacity_increment && queue_is_full(queue))
	{
		temelia_errno = FULL;
		return;
	}

	aux = doubly_linked_list_iterator_new(x, NULL, NULL);

	_ASSERT(aux, ==, NULL, NULL_POINTER,);

	if (queue_is_empty(queue))
	{
		queue->end = queue->begin = aux;
		queue->size = 1;
		return;
	}

	doubly_linked_list_iterator_join(aux, queue->end,
			doubly_linked_list_iterator_get_next(queue->end));
	queue->end = aux;

	queue->size++;
	if (queue->size > queue->capacity)
		queue->capacity = queue->size;
}

void *queue_get_begin(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, NULL);

	return queue->begin;
}

void *queue_get_end(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, NULL);

	return queue->end;
}

void queue_set_begin(queue_t queue, void *begin)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	queue->begin = begin;
}

void queue_set_end(queue_t queue, void *end)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	queue->end = end;
}

void *queue_get_front(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, NULL);

	return doubly_linked_list_iterator_get_key(queue->begin);
}

void *queue_get_back(queue_t queue)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER, NULL);

	return doubly_linked_list_iterator_get_key(queue->end);
}

void queue_pop_front(queue_t queue)
{
	doubly_linked_list_iterator_t aux;

	_ASSERT(queue_is_empty(queue), !=, 0, (INVALID_INPUT | EMPTY),);

	if (queue->size == 1)
	{
		queue->size = 0;

		_ASSERT(queue->begin, !=, queue->end, INVALID_INPUT,);

		doubly_linked_list_iterator_delete(queue->end);
		queue->end = queue->begin = NULL;
		return;
	}

	aux = queue->begin;
	queue->begin = doubly_linked_list_iterator_get_prev(queue->begin);
	doubly_linked_list_iterator_set_next(queue->begin, NULL);
	queue->size--;

	doubly_linked_list_iterator_delete(aux);
}

void queue_pop_back(queue_t queue)
{
	doubly_linked_list_iterator_t aux;

	_ASSERT(queue_is_empty(queue), !=, 0, (INVALID_INPUT | EMPTY),);

	if (queue->size == 1)
	{
		queue->size = 0;

		_ASSERT(queue->begin, !=, queue->end, INVALID_INPUT,);

		_delete(queue->end);
		queue->end = queue->begin = NULL;
		return;
	}

	aux = queue->end;
	queue->end = doubly_linked_list_iterator_get_next(queue->end);
	doubly_linked_list_iterator_set_prev(queue->end, NULL);
	queue->size--;

	doubly_linked_list_iterator_delete(aux);
}

void queue_set_capacity_increment(queue_t queue, int capacity_increment)
{
	_ASSERT(queue, ==, NULL, NULL_POINTER,);

	if (capacity_increment != 0 && capacity_increment != 1)
	{
		temelia_errno = INVALID_INPUT;
		return;
	}

	queue->capacity_increment = capacity_increment;
}

void queue_iterate(queue_t queue, void key_handler(void *key, void *context),
		void *context, int order)
{
	doubly_linked_list_iterator_t aux;

	_ASSERT(queue, ==, NULL, NULL_POINTER,);
	_ASSERT(key_handler, ==, NULL, NULL_POINTER,);

	if (order == 1)
		for (aux = queue->begin; aux != NULL; aux
				= doubly_linked_list_iterator_get_prev(aux))
			key_handler(doubly_linked_list_iterator_get_key(aux), context);

	else if (order == -1)
		for (aux = queue->end; aux != NULL; aux
				= doubly_linked_list_iterator_get_next(aux))
			key_handler(doubly_linked_list_iterator_get_key(aux), context);
}
